# -*- coding: utf-8 -*-
# @Time     : 2020/8/20 3:18 下午
# @Author   : huangxiong
# @FileName : deprecating_ret_decomposition.py
# @Comment  : 组合收益分解的基础函数
# @Software : PyCharm
import pandas as pd

from quant_researcher.quant.performance_attribution.core_functions.holding_analysis.allocation_analysis \
    import holding_based_allocation
from quant_researcher.quant.project_tool import time_tool
from quant_researcher.quant.datasource_fetch.portfolio_api import portfolio_tool
from quant_researcher.quant.project_tool.celebrity import get_lst_condition
from quant_researcher.quant.project_tool.db_operator import db_conn
from quant_researcher.quant.project_tool.logger.my_logger import LOG
from quant_researcher.quant.datasource_fetch.fund_api.fund_info import get_fund_sname
from quant_researcher.quant.datasource_fetch.fund_api.fund_nav_related import get_fund_return
from quant_researcher.quant.project_tool.exception import FOFServerError, ERR_S_2
from quant_researcher.quant.datasource_fetch.index_api import index_info
from server.tool.common_functions import get_nav_or_ret_series

FUND_RET_TABLE = 'mf_di_fndretstats_day'
INDEX_HQ_TABLE = 'mf_di_idxdailyreturn'


def get_fund_ret(portfolio_id, calc_month):
    """
    得到组合中子基金的收益率和相比业绩基准的超额收益

    :param portfolio_id: 组合ID
    :param calc_month: 计算月份，格式形如"202007"
    :return: pd.DataFrame
    """
    calc_date = time_tool.get_the_end_of_this_month(calc_month)
    tmp_df = portfolio_tool.get_portfolio_fund_weight(portfolio_id, calc_date)
    if tmp_df is None:
        LOG.error(f"组合{portfolio_id}的数据有问题")
        return

    fund_list = tmp_df['fund_code'].tolist()
    conn_ty = db_conn.get_derivative_data_conn()
    fund_where_condition = get_lst_condition(fund_list, 'fund_code')

    latest_date = pd.read_sql(f"select max(end_date) "
                              f"from {FUND_RET_TABLE} "
                              f"where {fund_where_condition} "
                              f"and end_date <= '{calc_date}' "
                              f"and period_interval = 2 ", conn_ty).iloc[0, 0]
    if latest_date is None:
        LOG.error(f"没有找到{fund_list}在{calc_date}之前的信息")
        conn_ty.close()
        return

    df = pd.read_sql(f"select fund_code, index_code, ret, active_ret "
                     f"from {FUND_RET_TABLE} "
                     f"where {fund_where_condition} "
                     f"and end_date = '{latest_date}' "
                     f"and period_interval = 2 ", conn_ty)

    df['index_ret'] = df['ret'] - df['active_ret']

    fund_name_df = get_fund_sname()
    df = df.merge(fund_name_df, how='left', on='fund_code')

    conn_ty.close()
    return df


def get_index_ret(index_code, start_date, end_date):
    """
    计算指数在某段时间内的收益率

    :param index_code: 指数代码
    :param start_date: 开始时间，格式"20190710"
    :param end_date: 结束时间，格式"20200710"
    :return:
    """
    conn = db_conn.get_derivative_data_conn()
    bench_hq = pd.read_sql(f"select day as tj, price_to "
                           f"from {INDEX_HQ_TABLE} "
                           f"where index_code = '{index_code}' "
                           f"and day >= '{start_date}' "
                           f"and day <= '{end_date}' "
                           f"order by day ", conn)
    if bench_hq.empty:
        LOG.error(f"没有找到{index_code}在{start_date}到{end_date}之间的涨跌幅信息")
        conn.close()
        return
    bench_ret = bench_hq['price_to'].iloc[-1] / bench_hq['price_to'].iloc[0] - 1
    conn.close()
    return bench_ret


def get_fund_ret_attrib_4_report(portfolio_id, calc_month):
    """
    得到组合的各基金的收益贡献率，组合投资报告用

    :param portfolio_id: 组合ID
    :param calc_month: 计算月份，格式形如"202007"
    :return: pd.DataFrame
    """
    start_date = time_tool.get_the_end_of_last_month(calc_month+'01')
    tmp_df = portfolio_tool.get_portfolio_hold_related_info(portfolio_id, start_date)
    if tmp_df is None:
        return
    start_date = tmp_df['backup_date'].iloc[0]
    end_date = time_tool.get_the_end_of_this_month(calc_month)

    weight_df = portfolio_tool.get_portfolio_fund_weight(portfolio_id, start_date, end_date)
    if weight_df is None:
        return
    fund_list = list(weight_df.columns)

    # ret_df = get_fund_daily_return(fund_list, time_tool.format_date_str(start_date, '%Y-%m-%d'),
    #                                time_tool.format_date_str(end_date, '%Y-%m-%d'), return_only_price_col=False)
    ret_df = get_fund_return(
        fund_code=fund_list,
        start_date=time_tool.format_date_str(start_date, '%Y-%m-%d'),
        end_date=time_tool.format_date_str(end_date, '%Y-%m-%d')
    )
    ret_df['end_date'] = pd.to_datetime(ret_df['end_date'])
    ret_df['end_date'] = ret_df['end_date'].dt.date
    ret_df = ret_df.rename(columns={"end_date": 'tj', "daily_return": 'f_price'})
    if ret_df.empty:
        return
    ret_df['tj'] = ret_df['tj'].astype(str).apply(lambda x: x.replace('-', ''))
    ret_df = ret_df.set_index(['tj', 'fund_code'])['f_price'].unstack()
    ret_df = ret_df.ffill().bfill()
    weight_df = weight_df.reindex(ret_df.index).reindex(columns=ret_df.columns)

    nav_df = portfolio_tool.get_portfolio_nav_related_info(portfolio_id, start_date, end_date)
    if nav_df is None:
        return
    nav_df = nav_df[['trade_date', 'account_net_value']]
    nav_df = nav_df.rename(columns={'trade_date': 'tj', 'account_net_value': 'nav'})
    nav_df = nav_df.set_index('tj').shift().reindex(ret_df.index)

    # 每支基金当天在组合的权重乘以基金的收益率乘以组合前一天净值
    # 再把每天数据相加得到每个基金总收益贡献，除以上月最后一天净值得到当月收益贡献率
    df = (ret_df * weight_df).mul(nav_df['nav'], axis=0)
    df = (df.sum() / nav_df.iloc[0, 0]).rename('ret_attrib').reset_index()

    f_weight = weight_df.loc[weight_df.index[-1]].rename('fund_weight').reset_index()
    df = df.merge(f_weight, how='left', on='fund_code')

    name_df = get_fund_sname()
    df = df.merge(name_df, how='left', on='fund_code')
    df = df[['fund_code', 'fund_sname', 'fund_weight', 'ret_attrib']]

    return df


def get_portfolio_ret_decomposition(portfolio_id, start_date, end_date,
                                    customer_create, index_code, customer_index):
    """
    得到一段时间组合的收益分解情况

    :param portfolio_id: 组合ID
    :param start_date: 开始时间，格式"20190710"
    :param end_date: 结束时间，格式"20200710"
    :param customer_create: 是否是用户自建指数，1-是，0-否，默认0
    :param index_code: 业绩基准代码
    :param customer_index: 用户自建指数，customer_create=1时必须传入该参数。
                           需要传入构建自建指数所需要的指数代码及权重，格式{"CBA00301.CS": 0.5, "000300": 0.5}
    :return:
    """
    # 得到组合这段时间的累计收益率
    p_nav = portfolio_tool.get_portfolio_nav_related_info(portfolio_id, start_date, end_date)
    if p_nav is None:
        raise FOFServerError(ERR_S_2, f"未找到{portfolio_id}组合的净值信息")
    # 组合总收益
    p_nav = p_nav[['trade_date', 'account_net_value']]
    p_nav = p_nav.rename(columns={'trade_date': 'tj', 'account_net_value': 'nav'})
    total_ret = p_nav['nav'].iloc[-1] / p_nav['nav'].iloc[0] - 1

    # 组合各基金每天的权重和收益率
    f_weight = portfolio_tool.get_portfolio_fund_weight(portfolio_id, start_date, end_date)
    if f_weight is None:
        raise FOFServerError(ERR_S_2, f"未找到{portfolio_id}组合的持仓信息")
    fund_list = list(f_weight.columns)

    f_ret = get_fund_return(
        fund_code=fund_list,
        start_date=time_tool.format_date_str(start_date, '%Y-%m-%d'),
        end_date=time_tool.format_date_str(end_date, '%Y-%m-%d')
    )
    f_ret = f_ret.rename(columns={"end_date": 'tj', "daily_return": 'f_price'})
    if f_ret.empty:
        raise FOFServerError(ERR_S_2, f"未找到组合中的基金{fund_list}的收益率数据")
    f_ret['tj'] = f_ret['tj'].apply(lambda x: x.replace('-', ''))
    f_ret = f_ret.set_index(['tj', 'fund_code'])['f_price'].unstack()
    f_ret = f_ret.ffill().bfill()

    f_weight = f_weight.reindex(f_ret.index).reindex(columns=f_ret.columns)
    # 持仓收益，基金权重和收益率加权求每天的收益率，再累乘
    hold_ret = ((f_ret * f_weight).sum(axis=1) + 1).product() - 1
    # 交易收益 = 总收益 - 持仓收益
    trade_ret = total_ret - hold_ret

    # 计算基准的收益率
    if customer_create == 0:
        b_w_df = index_info.get_index_asset_allocation(index_code)
        if b_w_df is None:
            raise FOFServerError(ERR_S_2, f"未找到基准{index_code}的股债占比数据")
        bench_ret = get_index_ret(index_code, start_date, end_date)
        if bench_ret is None:
            raise FOFServerError(ERR_S_2, f"未找到基准{index_code}的收益率数据")
        bench_stock_weight = b_w_df['stock']
        bench_bond_weight = b_w_df['bond']
    else:
        b_list = []
        for i in customer_index.keys():
            tmp_df = index_info.get_index_asset_allocation(i)
            if tmp_df is not None:
                b_list.append(tmp_df)
        b_w_df = pd.DataFrame(b_list)
        if b_w_df.empty:
            raise FOFServerError(ERR_S_2, f"未找到基准{customer_index}的股债占比数据")
        b_w_df.index = customer_index.keys()
        for i in customer_index.keys():
            b_w_df.loc[i, :] = b_w_df.loc[i, :] * customer_index[i]
        b_w_df = b_w_df.sum()
        bench_stock_weight = b_w_df.loc['stock']
        bench_bond_weight = b_w_df.loc['bond']
        bench_ret = get_nav_or_ret_series(7, None, '00', 'nav', start_date, end_date,
                                          customer_index=customer_index)
        if bench_ret is None:
            raise FOFServerError(ERR_S_2, f"未找到基准{customer_index}的收益率数据")
        bench_ret = bench_ret[-1] / bench_ret[0] - 1

    stock_bench_ret = bench_ret * bench_stock_weight
    bond_bench_ret = bench_ret * bench_bond_weight

    # 主动收益 = 持仓收益 - 基准收益
    active_ret = hold_ret - bench_ret

    # 得到每个基金最新的大类资产配置情况
    f_asset_df = holding_based_allocation.get_asset_allocation(fund_list, end_date=end_date)
    f_asset_df = f_asset_df[['stock', 'bond']].T
    f_asset_df = f_asset_df.reindex(columns=f_weight.columns)
    f_asset_df = f_asset_df.fillna(0)
    # 计算这段时间组合的加权平均股债权重
    p_weight = f_weight.mean()
    p_stock_weight = (p_weight * f_asset_df.loc['stock']).sum()
    p_bond_weight = (p_weight * f_asset_df.loc['bond']).sum()

    # 股票收益 = (组合股票占比 - 基准股票占比) * 基准股票收益率
    stock_ret = (p_stock_weight - bench_stock_weight) * stock_bench_ret
    # 债券收益 = (组合债券占比 - 基准债券占比) * 基准债券收益率
    bond_ret = (p_bond_weight - bench_bond_weight) * bond_bench_ret
    # 其他收益 = 主动收益 - 股票收益 - 债券收益
    other_ret = active_ret - stock_ret - bond_ret

    res_df = pd.Series({'total_ret': total_ret, 'trade_ret': trade_ret, 'hold_ret': hold_ret, 'bench_ret': bench_ret,
                        'active_ret': active_ret, 'stock_ret': stock_ret, 'bond_ret': bond_ret, 'other_ret': other_ret})
    res_df = res_df.reset_index().rename(columns={'index': 'ret_type', 0: 'ret_value'})
    res_df['ret_value'] = res_df['ret_value'].round(6)
    res_list = res_df.to_dict(orient='records')
    return res_list


if __name__ == '__main__':
    # get_fund_ret_attrib_4_report(portfolio_id='1189', calc_month='202007')
    get_portfolio_ret_decomposition('1352', '20200601', '20200810', 0, 'TK50B50S', None)

